package org.coody.framework.logged.engine;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.coody.framework.logged.config.LoggedConfig;
import org.coody.framework.logged.constant.LevelConstant;
import org.coody.framework.logged.entity.LoggedEntity;
import org.coody.framework.logged.entity.LoggedWriteEntity;
import org.coody.framework.logged.function.iface.LoggedFunction;
import org.coody.framework.logged.function.invoke.FunctionInvoker;
import org.coody.framework.logged.io.LogWriter;
import org.coody.framework.logged.util.ExpressionUtil;

public class LoggedEngine {

	public static final ExecutorService THREAD_POOL = new ThreadPoolExecutor(6, 6, 10, TimeUnit.MILLISECONDS,
			new ArrayBlockingQueue<Runnable>(6), new ThreadFactory() {
				@Override
				public Thread newThread(Runnable r) {
					return new Thread(r, "logged_pool_" + r.hashCode());
				}
			}, new ThreadPoolExecutor.DiscardOldestPolicy());

	private LinkedBlockingQueue<LoggedEntity> queue = new LinkedBlockingQueue<LoggedEntity>();

	private List<FunctionInvoker> invokers = new ArrayList<FunctionInvoker>();

	private static final String PARAMETER_PATTEN = "\\$\\{.+?\\}";

	public LoggedEngine() {
		THREAD_POOL.execute(() -> {
			buildConfig();
			buildPatten();
			flushLogged();

		});
	}

	private void buildConfig() {
		try {

			Field[] fields = LoggedConfig.class.getDeclaredFields();

			for (Field field : fields) {
				if (Modifier.isFinal(field.getModifiers())) {
					continue;
				}
				String configField = LoggedConfig.prefix + "." + field.getName();

				String value = System.getProperty(configField);
				if (value == null) {
					continue;
				}
				field.setAccessible(true);
				if (Integer.class.isAssignableFrom(field.getType())) {
					field.set(new LoggedConfig(), Integer.valueOf(value.trim()));
					continue;
				}
				if (Boolean.class.isAssignableFrom(field.getType())) {
					field.set(new LoggedConfig(), Boolean.valueOf(value.trim()));
					continue;
				}

				field.set(new LoggedConfig(), value.trim());
				continue;
			}

		} catch (Exception e) {
			// TODO: handle exception
		}
	}

	private void buildPatten() {
		if (LoggedConfig.functions != null && LoggedConfig.functions.trim().length() > 0) {
			String[] functions = LoggedConfig.functions.split(",");
			for (String line : functions) {
				try {
					Class<?> clazz = Class.forName(line.trim());
					LoggedFunction function = (LoggedFunction) clazz.newInstance();
					LoggedFunction.register(function.getName(), function);
				} catch (Exception e) {
				}
			}
		}
		LoggedConfig.level = LoggedConfig.level.toUpperCase();

		String logged = LoggedConfig.pattern;

		List<String> parameters = ExpressionUtil.getParameters(logged, PARAMETER_PATTEN);

		for (String line : parameters) {
			Integer index = logged.indexOf(line);
			if (index > 0) {
				String part = logged.substring(0, index);
				FunctionInvoker invoker = new FunctionInvoker();
				invoker.setParameter(part);
				invoker.setFunction(LoggedFunction.get("APPEND"));
				invokers.add(invoker);
			}
			String name = ExpressionUtil.center(line, "\\{", "\\}");
			String parameter = null;
			if (name.contains("(")) {
				parameter = ExpressionUtil.center(line, "\\(", "\\)");
				name = name.substring(0, name.indexOf("("));
			}
			FunctionInvoker invoker = new FunctionInvoker();
			invoker.setParameter(parameter);
			invoker.setFunction(LoggedFunction.get(name.trim()));
			invokers.add(invoker);

			logged = logged.substring(index + line.length());
		}
	}

	private void flushLogged() {
		while (true) {
			try {
				LoggedEntity line = queue.take();

				if (LoggedConfig.level.equals(LevelConstant.ERROR)) {
					if (!line.getLevel().equals(LevelConstant.ERROR)) {
						continue;
					}
				}
				if (LoggedConfig.level.equals(LevelConstant.INFO)) {
					if (line.getLevel().equals(LevelConstant.DEBUG)) {
						continue;
					}
				}
				if (line.getMsg() != null&&line.getParameter()!=null) {
					line.setMsg(String.format(line.getMsg(), line.getParameter()));
				}

				StringBuilder log = new StringBuilder();
				for (FunctionInvoker invoker : invokers) {
					log.append(invoker.getFunction().invoke(invoker.getParameter(), line));
				}
				if (line.getEx() != null) {
					log.append("\r\n");
					try {
						ByteArrayOutputStream output = new ByteArrayOutputStream();
						PrintStream printStream = new PrintStream(output);
						line.getEx().printStackTrace(printStream);
						log.append(output.toString());
						output.close();
						printStream.close();
					} catch (Exception e) {
						log.append(e.toString());
					}
				}

				log.append("\r\n");

				LoggedWriteEntity data = new LoggedWriteEntity();
				data.setLevel(line.getLevel());
				data.setMsg(log.toString());
				LogWriter.offer(data);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private void write(String msg, String level, Throwable ex, Object... parameter) {

		LoggedEntity line = new LoggedEntity(msg, level, ex, parameter, Thread.currentThread().getStackTrace());

		queue.offer(line);
	}

	public void debug(String msg, Object... parameter) {
		write(msg, LevelConstant.DEBUG, null, parameter);
	}

	public void error(Throwable ex) {
		write(null, LevelConstant.ERROR, null, new Object[] {});
	}

	public void error(String msg, Object... parameter) {
		write(msg, LevelConstant.ERROR, null, parameter);
	}

	public void info(String msg, Object... parameter) {
		write(msg, LevelConstant.INFO, null, parameter);
	}

	public void error(String msg, Throwable ex, Object... parameter) {
		write(msg, LevelConstant.ERROR, ex, parameter);
	}

}
